<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
	xmlns:fn="http://www.w3.org/2005/xpath-functions"
	xmlns:xs="http://www.w3.org/2001/XMLSchema"
	xmlns:data="data">
	
	<xsl:output method="text" encoding="UTF-8" indent="no"/>
	
	<xsl:param name="package"/>
	
	<xsl:template match="/entities">
/*
 *	Generated file, do not modify.
 */
package <xsl:value-of select="$package"/>;

import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Text;
import com.google.appengine.api.datastore.Link;
import com.google.appengine.api.datastore.Email;

/**
 *	Data entities.
 */
public abstract class Data {

	/**
	 *	Wrapped appengine datastore entity.
	 */
	protected final Entity entity;

	/**
	 *	@param parent Parent data, may be null.
	 *  @param entity wrapped entity, must not be null.
	 */
	protected Data(Entity entity) {
		this.entity = entity;
	}
	
	/**
	 *	handles "modified" state of the entity.
	 */	
	protected boolean modified = false;
	
	/**
	 *	@return true if the entity is modified.
	 */
	public boolean modified() {
		return modified;
	}
	
	/**
	 *	Utility method to enforce the "modified" flag.
	 */
	public void unmodify() {
		this.modified = false;
	}
	
	/**
	 *	Utility method to enforce the "modified" flag.
	 */
	public void modify() {
		this.modified = true;
	}
	
	/**
	 *	@return the wrapped entity.
	 */	
	public Entity getEntity() {
		return entity;
	}
	
	/**
	 *	@return the wrapped entity key.
	 */
	public Key getKey() {
		return entity.getKey();
	}

	/**
	 *	Ensures that properties are at least set to null.
	 */	
	public abstract void ensureNotNull();


	/**
	 *	Utility method to create a map of Data using their key.
	 */
	public static &lt;T extends Data&gt; Map&lt;Key, T&gt; map(Collection&lt;T&gt; datas) {
		final Map&lt;Key, T&gt; map = new HashMap&lt;Key, T&gt;();
		for(T data : datas) {
			if(data != null) {
				map.put(data.getKey(), data);
			}
		}
		return map;
	}
	
	public String toString() {
		return entity.toString();	
	}
	
	/**
	 *	Describe a Data property
	 */	
	public interface Properties {
		
		public String getName();
		
		public boolean isIndexed();
		
		public String getFullName();
	}
	
	/*
	 *	Entities
	 */
	
		<xsl:apply-templates select="entity"/>
}
	</xsl:template>	
	
	

	<!-- Types shortcuts translation -->
	<xsl:function name="data:type" as="xs:string?">
		<xsl:param name="type" as="xs:string?"/>
		<xsl:choose>
			<xsl:when test="ends-with($type, '[]')"><xsl:value-of select="concat('List&lt;', data:type(substring($type, 1, string-length($type) - 2)), '&gt;')"/></xsl:when>
			<xsl:when test="$type = 'string'">String</xsl:when>
			<xsl:when test="$type = 'object'">Object</xsl:when>
			<xsl:when test="$type = 'long'">Long</xsl:when>
			<xsl:when test="$type = 'long'">Integer</xsl:when>
			<xsl:when test="$type = 'short'">Short</xsl:when>
			<xsl:when test="$type = 'float'">Float</xsl:when>
			<xsl:when test="$type = 'double'">Double</xsl:when>
			<xsl:when test="$type = 'blob'">com.google.appengine.api.datastore.Blob</xsl:when>
			<xsl:when test="$type = 'bytes'">com.google.appengine.api.datastore.ShortBlob</xsl:when>
			<xsl:when test="$type = 'category'">com.google.appengine.api.datastore.Category</xsl:when>
			<xsl:when test="$type = 'key' or starts-with($type, 'key:')">Key</xsl:when>
			<xsl:when test="$type = 'boolean'">Boolean</xsl:when>
			<xsl:when test="$type = 'date'">java.util.Date</xsl:when>
			<xsl:when test="$type = 'text'">String</xsl:when>
			<xsl:when test="$type = 'email'">String</xsl:when>
			<xsl:when test="$type = 'url'">String</xsl:when>
			<xsl:otherwise><xsl:value-of select="$type"/></xsl:otherwise>
		</xsl:choose>
	</xsl:function>

	<!-- Types shortcuts translation -->
	<xsl:function name="data:type2" as="xs:string?">
		<xsl:param name="type" as="xs:string?"/>
		<xsl:choose>
			<xsl:when test="ends-with($type, '[]')"><xsl:value-of select="data:type(substring($type, 1, string-length($type) - 2))"/></xsl:when>
			<xsl:otherwise><xsl:value-of select="data:type($type)"/></xsl:otherwise>
		</xsl:choose>
	</xsl:function>	
	
	<xsl:function name="data:capitalize" as="xs:string?" >
		<xsl:param name="arg" as="xs:string?"/> 
		<xsl:sequence select="concat(upper-case(substring($arg,1,1)), substring($arg,2))"/>
	</xsl:function>

	<!-- 
		Generate code for an entity 
	-->
	<xsl:template match="entity">
	
	/**
	 *	Data class for Entity "<xsl:value-of select="@name"/>"<xsl:if test="parent::entity"> 
	 *	which has entity "<xsl:value-of select="parent::entity/@name"/>" as parent</xsl:if>.
	 */
	public static class <xsl:value-of select="@name"/> extends Data {
		
		<xsl:if test="parent::entity">
		/**
		 *	Parent class.
		 */
		public final static Class&lt;<xsl:value-of select="parent::entity/@name"/>&gt; __PARENT = Data.<xsl:value-of select="parent::entity/@name"/>.class;
		</xsl:if>
				
		/**
		 *	Entity __KIND for datastore.
		 */
		public final static String __KIND = "<xsl:value-of select="@name"/>";
		
		<xsl:choose>
			<xsl:when test="parent::entity">
		/**
		 * Create a key for <xsl:value-of select="@name"/> entity.
		 */
		public static Key createKey(Key parentKey, long id) {
			return KeyFactory.createKey(parentKey, __KIND, id);		
		}

		/**
		 * Create a key for <xsl:value-of select="@name"/> entity.
		 */
		public static Key createKey(Key parentKey, String id) {
			return KeyFactory.createKey(parentKey, __KIND, id);
		}
			</xsl:when>		
			<xsl:otherwise>
		/**
		 * Create a key for <xsl:value-of select="@name"/> entity.
		 */
		public static Key createKey(long id) {
			return KeyFactory.createKey(__KIND, id);		
		}

		/**
		 * Create a key for <xsl:value-of select="@name"/> entity.
		 */
		public static Key createKey(String id) {
			return KeyFactory.createKey(__KIND, id);
		}
			</xsl:otherwise>
		</xsl:choose>
		
		/**
		 *	Find the <xsl:value-of select="@name"/> key in a key hierarchy.
		 */
		public static Key getKey(Key key) {
			if(key == null) return null;
			if(!__KIND.equals(key.getKind())) return getKey(key.getParent());
			return key;
		}
		
		<!-- 
			Generate Properties meta model.
		-->
		public static enum Properties implements Data.Properties {
	
			<xsl:for-each select="property[not(parent::entity/@timestamp) or name() != parent::entity/@timestamp]">
				<xsl:value-of select="upper-case(@name)"/>("<xsl:value-of select="@name"/>", <xsl:value-of select="if(@indexed = 'true') then 'true' else 'false'"/>),
			</xsl:for-each>
			<xsl:if test="@timestamp">
				<xsl:value-of select="upper-case(@timestamp)"/>("<xsl:value-of select="@timestamp"/>", true)
			</xsl:if>
			;


			private final String name;

			public String getName() {
				return name;
			}

			private final boolean indexed;
		
			public boolean isIndexed() {
				return indexed;
			}
		
			private Properties(String name, boolean indexed) {
				this.name = name;
				this.indexed = indexed;
			}

			public String getFullName() {
				return "<xsl:value-of select="parent::entity/@name"/>" + name;
			}		
		}
		
		<!-- - - - - - - - - - - - 
			Generates constructor
		 - - - - - - - - - - -  -->

		/**
		 *  @param entity entity to map.
		 */
		public <xsl:value-of select="@name"/>(Entity entity) {
			super(entity);
		}

		<xsl:choose>
			<xsl:when test="parent::entity">
		
		/**
		 *	Used for new data, the Entity is created "on the fly".
		 *	@param parent parent data.
		 */
		public <xsl:value-of select="@name"/>(<xsl:value-of select="parent::entity/@name"/> parent) {
			this(new Entity(__KIND, parent == null ? null : parent.getKey()));
		}

		/**
		 *	Used for new data, the Entity is created "on the fly".
		 *	@param parent parent data.
		 */
		public <xsl:value-of select="@name"/>(<xsl:value-of select="parent::entity/@name"/> parent, String keyName) {
			this(new Entity(__KIND, keyName, parent == null ? null : parent.getKey()));
		}

		/**
		 *	Used for new data, the Entity is created "on the fly".
		 *	@param parent parent data.
		 *  @param id Id of entity to create.
		 */
		public <xsl:value-of select="@name"/>(<xsl:value-of select="parent::entity/@name"/> parent, long id) {
			this(new Entity(__KIND, id, parent == null ? null : parent.getKey()));
		}
		
			</xsl:when>
			<xsl:otherwise>
	
		/**
		 *	Used for new data, the Entity is created "on the fly".
		 */
		public <xsl:value-of select="@name"/>() {
			super(new Entity(__KIND));
		}
		
		/**
		 *	Used for new data, the Entity is created "on the fly".
		 */
		public <xsl:value-of select="@name"/>(String keyName) {
			super(new Entity(__KIND, keyName));
		}
				
		/**
		 *	Used for new data, the Entity is created "on the fly".
		 */
		public <xsl:value-of select="@name"/>(long id) {
			super(new Entity(__KIND, id));
		}
			</xsl:otherwise>
		</xsl:choose>

		<!-- - - - - - - - - - - - 
			Generates getters / setters
		 - - - - - - - - - - -  -->
		
		<xsl:for-each select="property">
		
			/**
			 *	Reads the "<xsl:value-of select="@name"/>" property of the entity.
			 *  <xsl:if test="@description"><xsl:value-of select="@description"/>.</xsl:if>
			 */		
			public <xsl:value-of select="data:type(@type)"/> get<xsl:value-of select="data:capitalize(@name)"/>() {
				<xsl:choose>
					<xsl:when test="@type = 'text'">
				final Text value = (Text) entity.getProperty(Properties.<xsl:value-of select="upper-case(@name)"/>.getName());
				return value == null ? null : value.getValue();
					</xsl:when>
					<xsl:when test="@type = 'url'">
				final Link value = (Link) entity.getProperty(Properties.<xsl:value-of select="upper-case(@name)"/>.getName());
				return value == null ? null : value.getValue();
					</xsl:when>
					<xsl:when test="@type = 'email'">
				final Email value = (Email) entity.getProperty(Properties.<xsl:value-of select="upper-case(@name)"/>.getName());
				return value == null ? null : value.getEmail();
					</xsl:when>
					<xsl:otherwise>
				return (<xsl:value-of select="data:type(@type)"/>) entity.getProperty(Properties.<xsl:value-of select="upper-case(@name)"/>.getName());
					</xsl:otherwise>
				</xsl:choose>
			}

			<xsl:choose>
				<xsl:when test="starts-with(@type, 'key:') and not(ends-with(@type, '[]'))">
			/**
			 *	Writes the "<xsl:value-of select="@name"/>" property to the entity and flags it as "modified".
			 *  <xsl:if test="@description"><xsl:value-of select="@description"/>.</xsl:if>
			 */
			public void set<xsl:value-of select="data:capitalize(@name)"/>(Data.<xsl:value-of select="substring-after(@type, 'key:')"/> value) {
				set<xsl:value-of select="data:capitalize(@name)"/>(value == null ? null : value.getKey());
			}
			
			/**
			 *	Writes the "<xsl:value-of select="@name"/>" property to the entity and flags it as "modified".
			 *  <xsl:if test="@description"><xsl:value-of select="@description"/>.</xsl:if>
			 */
			public void set<xsl:value-of select="data:capitalize(@name)"/>(Key key) {
				if(key != null &amp;&amp; !Data.<xsl:value-of select="substring-after(@type, 'key:')"/>.__KIND.equals(key.getKind())) {
					throw new IllegalArgumentException("cannot set '" + key + "' as <xsl:value-of select="substring-after(@type, 'key:')"/> key");
				}
				entity.set<xsl:if test="not(@indexed = 'true')">Unindexed</xsl:if>Property(Properties.<xsl:value-of select="upper-case(@name)"/>.getName(), key);
				modified = true;
			}
				</xsl:when>
				<xsl:otherwise>
				
			/**
			 *	Writes the "<xsl:value-of select="@name"/>" property to the entity and flags it as "modified".
			 *  <xsl:if test="@description"><xsl:value-of select="@description"/>.</xsl:if>
			 */
			public void set<xsl:value-of select="data:capitalize(@name)"/>(<xsl:value-of select="data:type(@type)"/> value) {
				<xsl:choose>
					<xsl:when test="@type = 'text'">
				entity.setUnindexedProperty(Properties.<xsl:value-of select="upper-case(@name)"/>.getName(), value == null ? null : new Text(value));
					</xsl:when>
					<xsl:when test="@type = 'url'">
				entity.set<xsl:if test="not(@indexed = 'true')">Unindexed</xsl:if>Property(Properties.<xsl:value-of select="upper-case(@name)"/>.getName(), value == null ? null : new Link(value));
					</xsl:when>
					<xsl:when test="@type = 'email'">
				entity.set<xsl:if test="not(@indexed = 'true')">Unindexed</xsl:if>Property(Properties.<xsl:value-of select="upper-case(@name)"/>.getName(), value == null ? null : new Email(value));
					</xsl:when>
					<xsl:otherwise>
				entity.set<xsl:if test="not(@indexed = 'true')">Unindexed</xsl:if>Property(Properties.<xsl:value-of select="upper-case(@name)"/>.getName(), value);
					</xsl:otherwise>
				</xsl:choose>
				modified = true;
			}
				</xsl:otherwise>
			</xsl:choose>

			<xsl:if test="ends-with(@type, '[]')">
			
			private <xsl:value-of select="data:type(@type)"/> deferred<xsl:value-of select="data:capitalize(@name)"/>List = null;
			/**
			 * Idem as {@link #get<xsl:value-of select="data:capitalize(@name)"/>()} but set a <xsl:value-of select="data:type(@type)"/> when getting a null <xsl:value-of select="data:type(@type)"/> from entity.
			 * non threadsafe, you should not use this kind of objects in multi thread environnements
			 */
			public <xsl:value-of select="data:type(@type)"/> getNonNull<xsl:value-of select="data:capitalize(@name)"/>() {
				<xsl:value-of select="data:type(@type)"/> list = (<xsl:value-of select="data:type(@type)"/>) entity.getProperty("<xsl:value-of select="@name"/>");
				if(list == null) {
					// This list will add itself automatically to the Data object if a new item is added
					list = deferred<xsl:value-of select="data:capitalize(@name)"/>List != null 
						? deferred<xsl:value-of select="data:capitalize(@name)"/>List
						: new ArrayList&lt;<xsl:value-of select="data:type2(@type)"/>&gt;() {
							@Override
							public void add(int index, <xsl:value-of select="data:type2(@type)"/> element) {
								if(get<xsl:value-of select="data:capitalize(@name)"/>() == null) {
									set<xsl:value-of select="data:capitalize(@name)"/>(this);
								}
								super.add(index, element);
							}
							
							@Override
							public boolean add(<xsl:value-of select="data:type2(@type)"/> e) {
								if(get<xsl:value-of select="data:capitalize(@name)"/>() == null) {
									set<xsl:value-of select="data:capitalize(@name)"/>(this);
								}
								return super.add(e);
							}						
							
							@Override
							public boolean addAll(Collection&lt;? extends <xsl:value-of select="data:type2(@type)"/>&gt; c) {
								if(get<xsl:value-of select="data:capitalize(@name)"/>() == null) {
									set<xsl:value-of select="data:capitalize(@name)"/>(this);
								}
								return super.addAll(c);
							}
							
							@Override
							public boolean addAll(int index,
									Collection&lt;? extends <xsl:value-of select="data:type2(@type)"/>&gt; c) {
								if(get<xsl:value-of select="data:capitalize(@name)"/>() == null) {
									set<xsl:value-of select="data:capitalize(@name)"/>(this);
								}
								return super.addAll(index, c);
							}
						};
					deferred<xsl:value-of select="data:capitalize(@name)"/>List = list;				
				}
				return list;
			}
			</xsl:if>
	
		</xsl:for-each>
		
		<xsl:for-each select="map">

			/**
			 *	Reads the "<xsl:value-of select="@name"/>" property of the entity.
			 */		
			public Map&lt;String, <xsl:value-of select="data:type(@type)"/>&gt; get<xsl:value-of select="data:capitalize(@name)"/>() {
				final Map&lt;String, <xsl:value-of select="data:type(@type)"/>&gt; map = new HashMap&lt;String, <xsl:value-of select="data:type(@type)"/>&gt;();
				for(Map.Entry&lt;String, Object&gt; entry : entity.getProperties().entrySet()) {
					final String key = entry.getKey();
					if(key != null &amp;&amp; key.startsWith("<xsl:value-of select="@name"/>__")) {
						map.put(key.substring("<xsl:value-of select="@name"/>__".length()), (<xsl:value-of select="data:type(@type)"/>) entry.getValue());
					}
				}
				return map.isEmpty() ? null : map;
			}
			
			/**
			 *	Reads the "<xsl:value-of select="@name"/>" property of the entity.
			 */		
			public <xsl:value-of select="data:type(@type)"/> get<xsl:value-of select="data:capitalize(@name)"/>(String index) {
				return (<xsl:value-of select="data:type(@type)"/>) entity.getProperty("<xsl:value-of select="@name"/>__" + index);
			}

			/**
			 *	Writes the "<xsl:value-of select="@name"/>" property to the entity and flags it as "modified".
			 */
			public void set<xsl:value-of select="data:capitalize(@name)"/>(String index, <xsl:value-of select="data:type(@type)"/> value) {
				entity.setUnindexedProperty("<xsl:value-of select="@name"/>__" + index, value);
				modified = true;
			}
		
		</xsl:for-each>
		
		<xsl:if test="@timestamp">
			/**
			 *	Returns the entity timestamp (updated at each dao.save)
			 */
			public java.util.Date get<xsl:value-of select="data:capitalize(@timestamp)"/>() {
				return (java.util.Date) entity.getProperty("<xsl:value-of select="@timestamp"/>");
			}
		</xsl:if>
		
		<!-- - - - - - - - - - - - 
			getters / setters for children parent-properties
		 - - - - - - - - - - -  -->
		<xsl:for-each select="entity[@parent-property]">
		
		private List&lt;<xsl:value-of select="@name"/>&gt; <xsl:value-of select="@parent-property"/> = new ArrayList&lt;<xsl:value-of select="@name"/>&gt;();
		 
		public void set<xsl:value-of select="data:capitalize(@parent-property)"/>(List&lt;<xsl:value-of select="@name"/>&gt; <xsl:value-of select="@parent-property"/>) {
			this.<xsl:value-of select="@parent-property"/> = <xsl:value-of select="@parent-property"/>;
		}

		public List&lt;<xsl:value-of select="@name"/>&gt; get<xsl:value-of select="data:capitalize(@parent-property)"/>() {
		 	return this.<xsl:value-of select="@parent-property"/>;
		}
		</xsl:for-each>
	
		/**
		 *	Ensure that properties are at least set to null.
		 */
		public void ensureNotNull() {
			<xsl:for-each select="property">
				if(!entity.hasProperty(Properties.<xsl:value-of select="upper-case(@name)"/>.getName())) {
					entity.set<xsl:if test="not(@indexed = 'true')">Unindexed</xsl:if>Property(Properties.<xsl:value-of select="upper-case(@name)"/>.getName(), null);
				}
			</xsl:for-each>			
		}
		
		/**
		 * Test if they have the same keys.
		 */
		public boolean equals(Object o) {
			if(o instanceof <xsl:value-of select="@name"/>) {
				final Key okey = ((<xsl:value-of select="@name"/>) o).getKey();
				final Key key = getKey();
				return key != null &amp;&amp; okey != null &amp;&amp; key.isComplete() 
					&amp;&amp; okey.isComplete() &amp;&amp; key.equals(okey);
			}
			return false;
		}
		
	}

		<xsl:apply-templates select="entity"/>
		
	</xsl:template>

		
</xsl:stylesheet>